<template>
  <view class="zs-canvas">
    <!-- #ifdef H5 -->
    <canvas
      class="canvas"
      canvas-id="zs1028"
      id="zs1028"
      @touchstart="_ontouchstart"
      @touchmove="_ontouchmove"
      @touchend="_ontouchend"
      @touchcancel="_ontouchcancel"
      @mousedown="_ontouchstart($event, true)"
      @mousemove="_ontouchmove($event, true)"
      @mouseup="_ontouchend(true)"
      :disable-scroll="disableScroll"
    ></canvas>
    <!-- #endif -->
    <!-- #ifdef MP-WEIXIN || MP-KUAISHOU || MP-JD -->
    <canvas
      class="canvas"
      id="zs1028"
      type="2d"
      @touchstart="_ontouchstart"
      @touchmove="_ontouchmove"
      @touchend="_ontouchend"
      @touchcancel="_ontouchcancel"
      :disable-scroll="disableScroll"
    ></canvas>
    <!-- #endif -->
    <!-- #ifndef H5 || MP-WEIXIN || MP-KUAISHOU || MP-JD -->
    <canvas
      class="canvas"
      canvas-id="zs1028"
      id="zs1028"
      @touchstart="_ontouchstart"
      @touchmove="_ontouchmove"
      @touchend="_ontouchend"
      @touchcancel="_ontouchcancel"
      :disable-scroll="disableScroll"
    ></canvas>
    <!-- #endif -->
  </view>
</template>

<script>
// #ifdef H5
const handleMousePoint = function (c, m, e) {
  let isMouse = m == true
  if (c.isTouchMode && isMouse) return false
  if (isMouse && e?.touches)
    e.touches.map((t) => {
      t.x = t.pageX - c._context.node.left
      t.y = t.pageY - c._context.node.top
    })
  return true
}
// #endif
export default {
  name: 'zs-canvas',
  props: {
    isUse: {
      type: Boolean,
      default: false,
    },
    disableScroll: {
      type: Boolean,
      default: false,
    },
  },
  data() {
    return {
      isTouchMode: true,
    }
  },
  mounted() {
    const canvasId = 'zs1028'
    uni
      .createSelectorQuery()
      .in(this)
      .select('#' + canvasId)
      .boundingClientRect((node) => {
        uni
          .createSelectorQuery()
          .in(this)
          .select('#' + canvasId)
          .fields(
            {
              size: true,
              node: true,
            },
            (res) => {
              const context = {
                canvasId,
                node,
                width: res.width,
                height: res.height,
                canvas: res.node || {
                  canvasId,
                  width: res.width,
                  height: res.height,
                },
                zs: {
                  canvasToTempFilePath: this.canvasToTempFilePath,
                  canvasGetImageData: this.canvasGetImageData,
                  canvasPutImageData: this.canvasPutImageData,
                },
              }
              context.plugin = context.zs
              // #ifdef H5
              this.isTouchMode = 'ontouchstart' in window
              // #endif
              // #ifdef MP-WEIXIN || MP-KUAISHOU || MP-JD
              /**
               * Create canvas context
               * @author TA远方
               * @link https://ext.dcloud.net.cn/publisher?id=752845
               */
              context.context = (function () {
                Object.assign(context.canvas, { width: context.width, height: context.height })
                const x = context.canvas.getContext('2d')
                const q = []
                const h = []
                const t = function (a) {
                  q.push(
                    Promise.resolve(function () {
                      a()
                    }),
                  )
                }
                const draw = function (reserve, callback) {
                  q.reduce(
                    function (pre, cur, ind) {
                      return Promise.resolve({
                        then(resolve, reject) {
                          pre
                            .then(function (b) {
                              b()
                              resolve(cur)
                            })
                            .catch(reject)
                        },
                      })
                    },
                    Promise.resolve(function () {
                      x.beginPath()
                      if (!reserve) x.clearRect(0, 0, context.width, context.height)
                    }),
                  )
                    .catch(function (e) {
                      console.error(e)
                    })
                    .then(function (a) {
                      a?.()
                      q.length = 0
                      callback?.()
                    })
                }
                const c = {
                  setFillStyle(color) {
                    t(function () {
                      x.fillStyle = color
                    })
                  },
                  setFontSize(fontSize) {
                    t(function () {
                      x.font = x.font.replace(/^d+/, fontSize)
                    })
                  },
                  setGlobalAlpha(alpha) {
                    t(function () {
                      x.globalAlpha = alpha
                    })
                  },
                  setLineCap(lineCap) {
                    t(function () {
                      x.lineCap = lineCap
                    })
                  },
                  setLineDash(pattern, offset) {
                    t(function () {
                      x.setLineDash(pattern, offset)
                    })
                  },
                  setLineJoin(lineJoin) {
                    t(function () {
                      x.lineJoin = lineJoin
                    })
                  },
                  setLineWidth(lineWidth) {
                    t(function () {
                      x.lineWidth = lineWidth
                    })
                  },
                  setMiterLimit(miterLimit) {
                    t(function () {
                      x.miterLimit = miterLimit
                    })
                  },
                  setShadow(offsetX, offsetY, blur, color) {
                    t(function () {
                      {
                        if (offsetX) x.shadowOffsetX = offsetX
                        if (offsetY) x.shadowOffsetY = offsetY
                        if (blur) x.shadowBlur = blur
                        if (color) x.shadowColor = color
                      }
                    })
                  },
                  setStrokeStyle(color) {
                    t(function () {
                      x.strokeStyle = color
                    })
                  },
                  setTextAlign(align) {
                    t(function () {
                      x.textAlign = align
                    })
                  },
                  setTextBaseline(textBaseline) {
                    t(function () {
                      x.textBaseline = textBaseline
                    })
                  },
                  setTransform(scaleX, skewX, skewY, scaleY, translateX, translateY) {
                    t(function () {
                      x.transform(scaleX, skewX, skewY, scaleY, translateX, translateY)
                    })
                  },
                  drawImage(imageResource, dx, dy, dWidth, dHeight, sx, sy, sWidth, sHeight) {
                    let b
                    switch (arguments.length) {
                      case 3:
                        b = function (i) {
                          x.drawImage(i, dx, dy)
                        }
                        break
                      case 5:
                        b = function (i) {
                          x.drawImage(i, dx, dy, dWidth, dHeight)
                        }
                        break
                      default:
                        b = function (i) {
                          x.drawImage(i, dx, dy, dWidth, dHeight, sx, sy, sWidth, sHeight)
                        }
                    }
                    let i = h.find((m) => m.a == imageResource)?.i
                    if (i) {
                      t(() => b(i))
                    } else {
                      q.push(
                        Promise.resolve({
                          then(e, t) {
                            i = context.canvas.createImage()
                            i.onerror = t
                            i.onload = function () {
                              h.push({ a: imageResource, i })
                              e(() => b(i))
                            }
                            i.src = imageResource
                          },
                        }),
                      )
                    }
                  },
                  draw,
                }
                ;[
                  'font',
                  'shadowOffsetX',
                  'shadowOffsetY',
                  'shadowBlur',
                  'shadowColor',
                  'fillStyle',
                  'globalAlpha',
                  'lineCap',
                  'lineJoin',
                  'lineWidth',
                  'lineDashOffset',
                  'miterLimit',
                  'strokeStyle',
                  'textAlign',
                  'textBaseline',
                ].forEach(function (n) {
                  Object.defineProperty(c, n, {
                    enumerable: true,
                    set(a) {
                      t(function () {
                        x[n] = a
                      })
                    },
                    get() {
                      x[n]
                    },
                  })
                })
                ;[
                  'arc',
                  'arcTo',
                  'bezierCurveTo',
                  'clip',
                  'beginPath',
                  'closePath',
                  'clearRect',
                  'createCircularGradient',
                  'createLinearGradient',
                  'createPattern',
                  'fill',
                  'fillRect',
                  'fillText',
                  'lineTo',
                  'measureText',
                  'moveTo',
                  'quadraticCurveTo',
                  'rect',
                  'restore',
                  'rotate',
                  'save',
                  'scale',
                  'stroke',
                  'strokeRect',
                  'strokeText',
                  'transform',
                  'translate',
                ].forEach(function (n) {
                  c[n] = function (...a) {
                    t(function () {
                      x[n](...a)
                    })
                  }
                })
                return c
              })()
              // #endif
              // #ifndef MP-WEIXIN || MP-KUAISHOU || MP-JD
              Object.assign(context.canvas, {
                /**
                 * getCanvasContext
                 * @param contextType 2d，webgl，webgl2任意
                 */
                getContext: (contextType) => uni.createCanvasContext(canvasId, this),
                createImage() {
                  // #ifdef H5
                  return new Image()
                  // #endif
                  // #ifndef H5
                  let img = { width: context.width, height: context.height, src: '' }
                  Object.defineProperty(img, 'src', {
                    set(value) {
                      img.onload?.(img)
                    },
                  })
                  return img
                  // #endif
                },
                cancelAnimationFrame(requestID) {
                  // #ifdef H5
                  window.cancelAnimationFrame(requestID)
                  // #endif
                },
                requestAnimationFrame(callback) {
                  // #ifdef H5
                  window.requestAnimationFrame(callback)
                  // #endif
                  // #ifndef H5
                  callback()
                  // #endif
                },
              })
              context.context = context.canvas.getContext()
              // #endif
              this._context = {
                canvasId,
                node,
                canvas: context.canvas,
                width: context.width,
                height: context.height,
              }
              if (this.isUse != true) {
                const label = '欢迎使用 <zs-canvas> 点此展开'
                console.groupCollapsed(label)
                console.table({
                  1: { 平台: 'H5', 支持动画: '流畅' },
                  2: { 平台: 'APP', 支持动画: '可能掉帧' },
                  3: { 平台: '微信小程序', 支持动画: '流畅' },
                  4: { 平台: '其它', 支持动画: '未确定' },
                })
                console.info('要关闭此提示和演示，请给组件<zs-canvas>设置属性:is-use="true"')
                console.info(
                  '%c 组件<zs-canvas>文档： https://ext.dcloud.net.cn/publisher?id=752845',
                  'color:orange;',
                )
                console.groupEnd(label)
                context.context.save()
                context.context.setTextAlign('center')
                context.context.setTextBaseline('middle')
                context.context.fillText('欢迎使用 <zs-canvas>', res.width / 2, res.height / 2)
                context.context.restore()
                context.context.draw(false, () => this.$emit('ready', context))
              } else this.$emit('ready', context)
            },
          )
          .exec()
      })
      .exec()
  },
  methods: {
    _ontouchstart(e, m) {
      // #ifdef H5
      if (!handleMousePoint(this, m, e)) return
      // #endif
      this.touchPoint = e
      this.$emit('touch_start', e)
    },
    _ontouchmove(e, m) {
      // #ifdef H5
      if (!this.touchPoint) return
      if (!handleMousePoint(this, m, e)) return
      // #endif
      this.touchPoint = e
      this.$emit('touch_move', e)
    },
    _ontouchend(m) {
      // #ifdef H5
      if (!this.touchPoint) return
      if (!handleMousePoint(this, m)) return
      // #endif
      this.$emit('touch_end', this.touchPoint)
      this.touchPoint = undefined
    },
    _ontouchcancel() {
      this.touchPoint = undefined
      this.$emit('touch_cancel')
    },
    /**
     * @param {} config - 不用传canvasId和canvas
     */
    canvasToTempFilePath(config) {
      config.canvasId = this._context.canvasId
      if (this._context.canvas) config.canvas = this._context.canvas
      uni.canvasToTempFilePath(config, this)
    },
    /**
     * @param {} config -不用传canvasId
     */
    canvasGetImageData(config) {
      // #ifdef MP-WEIXIN || MP-KUAISHOU || MP-JD
      try {
        const { x, y, width, height } = config
        const { canvas } = this._context
        // Uint8ClampedArray
        let ctx = canvas.getContext('2d')
        let data = ctx.getImageData(x, y, width, height)
        config.success?.(data)
      } catch (e) {
        config.fail?.(e)
      } finally {
        config.complete?.()
      }
      // #endif
      // #ifndef MP-WEIXIN || MP-KUAISHOU || MP-JD
      config.canvasId = this._context.canvasId
      if (this._context.canvas) config.canvas = this._context.canvas
      uni.canvasGetImageData(config, this)
      // #endif
    },
    /**
     * 不用传canvasId
     * @param { data } - data 与原来的uni api不同，必须是ImageData
     */
    canvasPutImageData(config) {
      // #ifdef MP-WEIXIN || MP-KUAISHOU || MP-JD
      try {
        const canvas = this._context.canvas
        let x = config.x || 0
        let y = config.y || 0
        let ctx = canvas.getContext('2d')
        if (config.width || config.height)
          ctx.putImageData(
            config.data,
            x,
            y,
            0,
            0,
            config.width || canvas.width,
            config.height || canvas.height,
          )
        else ctx.putImageData(config.data, x, y)
        config.success?.()
      } catch (e) {
        config.fail?.(e)
      } finally {
        config.complete?.()
      }
      // #endif
      // #ifndef MP-WEIXIN || MP-KUAISHOU || MP-JD
      if (config.data?.data) config.data = config.data.data
      config.canvasId = this._context.canvasId
      if (this._context.canvas) config.canvas = this._context.canvas
      uni.canvasPutImageData(config, this)
      // #endif
    },
  },
}
</script>

<style lang="scss">
/* #ifdef MP-WEIXIN */
zs-canvas {
  width: inherit;
  height: inherit;
  /* #endif */

  .zs-canvas {
    width: inherit;
    height: inherit;

    .canvas {
      width: inherit;
      height: inherit;
    }
  }

  /* #ifdef MP-WEIXIN */
}
/* #endif */
</style>
